/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Controls;

import Core.Control;
import Core.HSV;
import MathCore.CircularArray;
import MathCore.CircularArrayInt;
import MathCore.Expression;
import MathCore.Operand;
import MathCore.Operands.Complex;
import MathCore.Operands.Constant;
import MathCore.Operands.List;
import MathCore.Operands.Number;
import MathCore.Operands.Variable;
import MathCore.Operators.OperatorException;
import MathCore.Processor;
import MathCore.ProcessorException;
import MathCore.Real;
import Util.TimeStamp;
import java.util.Enumeration;
import java.util.Vector;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

/**
 *
 * @author Александр
 */
public class Plotter extends Control
{
	// параметры позиционирования
	private Real sx = new Real(0),  sy = new Real(0);	// сдвиг реальных данных
	private Real scale = new Real(16);					// масштаб
	private Real cx = new Real(0),  cy = new Real(0);	// сдвиг осей
	
	// параметры вычисления
	private int multiplier = 1;							// множитель вычисления
	private Variable var;								// переменная для изменения
	private Processor proc;								// вычислитель

	// константы сдвига
	public static int ShiftStep = 15;

	// буферы
	private PlotContainer containers[];
	private Image endImage;

	// флаги
	private boolean modelChanged = true;
	private boolean showLegend = true;

	// конструктор
	public Plotter(Vector expressions, Vector variables, Variable varForChange, Processor proc, int width, int height)
	{
		super(width, height);
		this.var = varForChange;
		this.proc = proc;

		// данные о сдвиге осей
		cx.assign(width / 2);
		cy.assign(height / 2);

		// инициализируем буферы
		endImage = Image.createImage(width, height);
		containers = new PlotContainer [expressions.size()];
		for(int i = 0; i<expressions.size(); i++)
		{
			try
			{
				containers[i] = new PlotContainer((Expression)expressions.elementAt(i), ((Variable)variables.elementAt(i)));
			}
			catch(ProcessorException e)
			{
				containers[i] = null;
			}
		}
			

		reset();
	}

	/*
	 * /////////////////////////////////////////////////////////////////////////
	 * Основные функции обсчета
	 */

	// вычислить шаг для обсчета
	private Real step()
	{
		Real r = new Real(1);
		r.div(scale);
		r.mul(multiplier);
		return r;
	}

	// сдвинуть все конечные координаты на определенную величину
	public void shiftY(int pixels)
	{
		Real delta = new Real(pixels);
		delta.div(scale);
		sy.add(delta);

		for(int i = 0; i < containers.length; i++)
		{
			if(containers[i] == null) continue;
			
			for(Enumeration e = containers[i].endCoords.elements(); e.hasMoreElements(); )
			{
				CircularArrayInt ca = (CircularArrayInt) e.nextElement();
				for(int j = 0; j<ca.lenght(); j++)
					if(ca.get(j) != Integer.MAX_VALUE)
						ca.set(j, ca.get(j) - pixels);
			}
		}

		modelChanged = true;
	}

	// досчитать необходимое количество данных
	public void shiftX(int pixels)
	{
		Real startx = null, endx = null, x;
		Real step = step();
		int addings = 0;

		if(pixels > 0) // сдвиг вправо
		{
			// узнаем стартовое значение Х
			startx = new Real(W);
			toLocalX(startx);
			startx.add(step);

			// узнаем конечную координату
			endx = new Real(W+pixels);
			toLocalX(endx);

			// dbg - счетчик добавлений
			addings = 0;

			// задаем цикл
			for(x = new Real(startx); x.lessEqual(endx); x.add(step))
			{
				// обновляем в ядре данные о переменной
				var.set(new Number(x));
				proc.setVariable(var);

				// учитываем сколько итераций прошло
				addings++;

				for(int i = 0; i < containers.length; i++)
				{
					if(containers[i] == null) continue;

					Operand res = null;
					try // пытаемся вычислить значение
					{
						res = proc.calculate(containers[i].expression);
					}
					catch (ProcessorException ex)
					{
						ex.printStackTrace();
						continue;
					}
					catch (OperatorException ex)
					{
						ex.printStackTrace();
						continue;
					}

					// проверяем, что ядро не западлило
					if(res == null) continue;

					// устанавливаем переменную ядра
					setCoreVariable(res, containers[i].var);

					// выворачиваем выходной операнд в список значений
					Vector reals = new Vector();
					expandToReals(res, reals);

					// проверям, что есть что добавлять
					if(reals.size() == 0) continue;

					// добавляем непосредственные значения
					int j = 0;
					for(Enumeration e = reals.elements(); e.hasMoreElements(); )
					{
						// получаем очередное число из списка
						Real r = (Real) e.nextElement();
						// добавляем число в список результатов
						containers[i].add(j, false, r);
						// преобразуем полученное число в конечную координату
						containers[i].add(j, false, translateToLocalY(r));
						// увеличиваем счетчик
						j++;
					}
				}
			}
		}
		else if(pixels < 0) // сдвиг влево
		{
			// узнаем стартовое значение Х
			startx = new Real(0);
			toLocalX(startx);
			startx.sub(step);

			// узнаем конечную координату
			endx = new Real(pixels);
			toLocalX(endx);

			// инвертируем шаг чтоб идти справа-налево
			step.mul(-1);

			// задаем цикл
			for(x = new Real(startx); x.greaterEqual(endx); x.add(step))
			{
				// обновляем в ядре данные о переменной
				var.set(new Number(x));
				proc.setVariable(var);

				for(int i = 0; i < containers.length; i++)
				{
					if(containers[i] == null) continue;

					Operand res = null;
					try // пытаемся вычислить значение
					{
						res = proc.calculate(containers[i].expression);
					}
					catch (ProcessorException ex)
					{
						ex.printStackTrace();
						continue;
					}
					catch (OperatorException ex)
					{
						ex.printStackTrace();
						continue;
					}

					// проверяем, что ядро не западлило
					if(res == null) continue;

					// устанавливаем переменную ядра
					setCoreVariable(res, containers[i].var);

					// выворачиваем выходной операнд в список значений
					Vector reals = new Vector();
					expandToReals(res, reals);

					// проверям, что есть что добавлять
					if(reals.size() == 0) continue;

					addings++;

					// добавляем непосредственные значения
					int j = 0;
					for(Enumeration e = reals.elements(); e.hasMoreElements(); )
					{
						// получаем очередное число из списка
						Real r = (Real) e.nextElement();
						// добавляем число в список результатов
						containers[i].add(j, true, r);
						// преобразуем полученное число в конечную координату
						containers[i].add(j, true, translateToLocalY(r));
						// увеличиваем счетчик
						j++;
					}
				}
			}
		}

		modelChanged = true;

		//System.out.println(addings + " addings for "+pixels+" px. shift");

		Real delta = new Real(-pixels);
		delta.div(scale);
		sx.add(delta);
	}

	// сброс и начальная инициализация всего массива
	public void reset()
	{
		sx.assign(0);
		sy.assign(0);
		recalc();

		modelChanged = true;
	}

	// пересчет
	private void recalc()
	{
		// замер времени
		TimeStamp stamp = new TimeStamp("recalc");

		// шаг
		Real step = step();

		// начальные и конечные  координаты
		Real startx = new Real(0);
		Real endx = new Real(W);
		toLocalX(startx);
		toLocalX(endx);

		// переменная для изменения
		Real x;

		// обнуляем данные в массивах
		for(int i = 0; i<containers.length; i++)
		{
			if(containers[i] == null) continue;

			// приводим данные о просчете в соответстие с реальностью
			containers[i].left.assign(startx);
			containers[i].right.assign(endx);

			// обнуляем массивы
			containers[i].reset();
		}

		for(x = new Real(startx); x.lessEqual(endx); x.add(step))
		{
			// обновляем в ядре данные о переменной
			var.set(new Number(x));
			proc.setVariable(var);

			for(int i = 0; i < containers.length; i++)
			{
				if(containers[i] == null) continue;

				Operand res = null;
				try // пытаемся вычислить значение
				{
					res = proc.calculate(containers[i].expression);
				}
				catch (ProcessorException ex)
				{
					ex.printStackTrace();
					containers[i] = null;
					continue;
				}
				catch (OperatorException ex)
				{
					ex.printStackTrace();
					containers[i] = null;
					continue;
				}

				// устанавливаем переменную ядра
				setCoreVariable(res, containers[i].var);

				// проверяем, что ядро не западлило
				if(res == null) continue;
				
				// выворачиваем выходной операнд в список значений
				Vector reals = new Vector();
				expandToReals(res, reals);

				// проверям, что есть что добавлять
				if(reals.size() == 0) continue;

				// добавляем непосредственные значения
				int j = 0;
				for(Enumeration e = reals.elements(); e.hasMoreElements(); )
				{
					// получаем очередное число из списка
					Real r = (Real) e.nextElement();
					// добавляем число в список результатов
					containers[i].add(j, false, r);
					// System.out.println("global y: "+r);
					// преобразуем полученное число в конечную координату
					containers[i].add(j, false, translateToLocalY(r));
					// System.out.println("local y: "+translateToLocalY(r));
					// увеличиваем счетчик
					j++;
				}
				
			}
		}

		modelChanged = true;

		stamp.show();
	}

	// вывернуть данные операнда в список Real'ов
	private void expandToReals(Operand source, Vector vector)
	{
		if(source == null) return;
		
		else if(source instanceof Number)
			vector.addElement(((Number)source).get());

		else if(source instanceof Complex) 
			vector.addElement(((Complex)source).Re());

		else if(source instanceof List)
		{
			List lst = (List) source;
			for(int i = 0; i<lst.size(); i++) expandToReals(lst.get(i), vector);
		}
		else if(source instanceof Variable)
			expandToReals(((Variable)source).get(), vector);

		else if(source instanceof Constant)
			expandToReals(((Constant)source).get(), vector);
	}

	// оттранслировать значение Real в int с учетом всяких подлянок
	private int translateToLocalY(Real r)
	{
		if(!r.isNan() && !r.isInfinity())
		{
			Real y = new Real(r);
			toGlobalY(y);
			return y.toInteger();
		}
		else
		{
			return Integer.MAX_VALUE;
		}
	}

	// устанавливаем переменную ядра
	private void setCoreVariable(Operand res, Variable var)
	{
		if(var != null && res != null)
		{
			var.set(res);
			proc.setVariable(var);
		}
	}

	// рендеринг графиков
	private void render()
	{
		Graphics g = endImage.getGraphics();

		g.setColor(0xffffff); // заполнить фон
		g.fillRect(0, 0, getW(), getH());

		if (scale.greaterEqual(4))
		{
			g.setColor(0xaaaaaa);  // вспомогательные оси серого цвета

			Real top = toLocalY(new Real(0));
			Real bottom = toLocalY(new Real(getH()));
			Real left = toLocalX(new Real(0));
			Real right = toLocalX(new Real(getW()));

			for (int i = bottom.toInteger(); i <= top.toInteger(); i++)
			{
				drawHorAxe(i, g);
			}
			for (int i = left.toInteger(); i <= right.toInteger(); i++)
			{
				drawVertAxe(i, g);
			}

			if (scale.greaterEqual(16))
			{
				g.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM));

				Real tx = new Real(), ty = new Real();

				ty.assign(0);
				toGlobalY(ty);
				for (int i = left.toInteger(); i <= right.toInteger(); i++)
				{
					tx.assign(i);
					toGlobalX(tx);
					g.drawString("" + i, tx.toInteger(), ty.toInteger(), Graphics.TOP | Graphics.RIGHT);
				}

				tx.assign(0);
				toGlobalX(tx);
				for (int i = bottom.toInteger(); i <= top.toInteger(); i++)
				{
					ty.assign(i);
					toGlobalY(ty);
					g.drawString("" + i, tx.toInteger(), ty.toInteger(), Graphics.TOP | Graphics.RIGHT);
				}
			}
		}

		g.setColor(0);  // основные оси черного цвета
		drawHorAxe(0, g);
		drawVertAxe(0, g);

		g.setColor(0xff0000);

		// перебираем контейнеры
		for(int i=0; i<containers.length; i++)
		{
			if(containers[i] == null) continue;

			HSV color = new HSV(i*(360/containers.length), 1, 0.8);
			g.setColor(color.toRGB());

			int x, y1, y2;
			for(Enumeration e = containers[i].endCoords.elements(); e.hasMoreElements();)
			{
				CircularArrayInt ca = (CircularArrayInt) e.nextElement();
				x = 0;
				for(int j = 0; j<ca.lenght()-1; j++)
				{
					y1 = ca.get(j);
					y2 = ca.get(j+1);
					
					if(y1 != Integer.MAX_VALUE && y2 != Integer.MAX_VALUE)
						g.drawLine(x, y1, x+multiplier, y2);
					
					x+=multiplier;
				}
			}
		}
	}

	/*
	 * Конец функций обсчета
	 * /////////////////////////////////////////////////////////////////////////
	 */

	// основная ф-ия отрисовки
	public void draw(Graphics g, int x, int y)
	{
		if(modelChanged) render();
		g.drawImage(endImage, x, y, Graphics.TOP | Graphics.LEFT);
	}

	// перевод координат
	private Real toGlobalX(Real local)
	{
		if (local.isNan())
		{
			return local;
		}

		local.add(sx);
		local.mul(scale);
		local.add(cx);
		return local;
	}

	private Real toGlobalY(Real local)
	{
		if (local.isNan())
		{
			return local;
		}

		local.add(sy);
		local.mul(scale);
		local.mul(-1);
		local.add(cy);
		return local;
	}

	private Real toLocalY(Real global)
	{
		global.sub(cy);
		global.mul(-1);
		global.div(scale);
		global.sub(sy);
		return global;
	}

	private Real toLocalX(Real global)
	{
		global.sub(cx);
		global.div(scale);
		global.sub(sx);
		return global;
	}

	public void zoomIn()
	{
		scale.mul(2);
		recalc();
	}

	public void zoomOut()
	{
		scale.div(2);
		recalc();
	}

	/*
	 * /////////////////////////////////////////////////////////////////////////
	 * Вспомогательные функции
	 */

	// нарисовать горизонтальнуь ось
	private void drawHorAxe(int y, Graphics g)
	{
		Real ry = new Real(y);
		ry.assign(toGlobalY(ry));

		if (ry.toInteger() < 0 || ry.toInteger() > getH())
		{
			return;
		}

		g.drawLine(0, ry.toInteger(), getW(), ry.toInteger());
	}

	// нарисовать вертикальную ось
	private void drawVertAxe(int x, Graphics g)
	{
		Real rx = new Real(x);
		rx.assign(toGlobalX(rx));

		if (rx.toInteger() < 0 || rx.toInteger() > getW())
		{
			return;
		}

		g.drawLine(rx.toInteger(), 0, rx.toInteger(), getH());
	}

	// проверить флаг легенды
	public boolean legendEnabled()
	{
		return showLegend;
	}

	// установить флаг легенды
	public void showLegend(boolean show)
	{
		showLegend = show;
		modelChanged = true;
	}

	// основной контейнер
	private class PlotContainer extends Object
	{
		public Expression expression;	// выражение для вычисления
		private Vector resultOparands;	// список результатов <Operand>
		private Vector resultReals;		//       -/-          <CAReal>
		public Vector endCoords;		// список конечных координат <CAInt>
		public Variable var;

		public Real right, left;

		public PlotContainer(Expression expression, Variable var) throws ProcessorException
		{
			this.expression = Processor.toRPN(expression);
			this.var = var;
			
			resultReals = new Vector();
			endCoords = new Vector();

			right = new Real(0);
			left = new Real(0);
		}

		public void add(int position, boolean atStart, Real real)
		{
			while(resultReals.size() < position+1)
				resultReals.addElement(new CircularArray(W));

			CircularArray ca = (CircularArray) resultReals.elementAt(position);
			if(atStart) ca.addToStart(real);
			else ca.addToEnd(real);
		}

		public void add(int position, boolean atStart, int coord)
		{
			while(endCoords.size() < position+1)
				endCoords.addElement(new CircularArrayInt(W));

			CircularArrayInt ca = (CircularArrayInt) endCoords.elementAt(position);
			if(atStart) ca.addToStart(coord);
			else ca.addToEnd(coord);
		}

		public void reset()
		{
			for(Enumeration e = resultReals.elements(); e.hasMoreElements(); )
				((CircularArray)e.nextElement()).reset();

			for(Enumeration e = endCoords.elements(); e.hasMoreElements(); )
				((CircularArrayInt)e.nextElement()).reset();
		}
	}
}
